
// Linux API
#include <sys/epoll.h>
#include <errno.h>
#include <unistd.h>
// C API
#include <assert.h>
// C STL
#include <string.h>

#include "Logger.hpp"
#include "EPollPoller.hpp"
#include "Channel.hpp"

namespace tulun
{
    // class EPollPoller : public tulun::Poller
    // using EventList = std::vector<struct epoll_event>;
    // int epollfd_;
    // EventList events_;
    // static const int kInitEventListSize = 16;

    static const int kNew = -1;
    static const int kAdded = 1;
    static const int KDeleted = 2;

    EPollPoller::EPollPoller(EventLoop *loop)
        : Poller(loop),
          epollfd_(::epoll_create1(EPOLL_CLOEXEC)),
          events_(kInitEventListSize)
    {
        if (epollfd_ < 0)
        {
            LOG_SYSFATAL << "epoll_create1 failed ";
        }
    }
    EPollPoller::~EPollPoller()
    {
        ::close(epollfd_);
    }
    Timestamp EPollPoller::poll(int timeoutMs, ChannelList *activeChannels)
    {
        int numEvents = ::epoll_wait(epollfd_, events_.data(), events_.size(), timeoutMs);
        int savedErrno = errno;
        tulun::Timestamp now(tulun::Timestamp::Now());
        if (numEvents > 0)
        {
            fillActiveChannels(numEvents, activeChannels);
            if (static_cast<size_t>(numEvents) == events_.size())
            {
                events_.resize(events_.size() * 2);
            }
        }
        else if (numEvents == 0)
        {
            LOG_TRACE << "nothing happend ";
        }
        else
        {
            if (savedErrno == EINTR)
            {
                errno = savedErrno;
                LOG_SYSERR << "EPollPoller::poll() ";
            }
        }
        return now;
    }
    void EPollPoller::updateChannel(Channel *channel)
    {
        const int index = channel->index();
        if (index == kNew || index == KDeleted)
        {
            int fd = channel->fd();
            if (index == kNew)
            {
                assert(channels_.find(fd) == channels_.end());
                channels_[fd] = channel;
            }
            else
            {
                assert(channels_.find(fd) != channels_.end());
                assert(channels_[fd] == channel);
            }
            channel->set_Index(kAdded);
            update(EPOLL_CTL_ADD, channel);
        }
        else
        {
            // index == kAdded
            int fd = channel->fd();
            assert(channels_.find(fd) != channels_.end());
            assert(channels_[fd] = channel);
            assert(index == kAdded);

            if (channel->isNoneEvent())
            {
                update(EPOLL_CTL_DEL, channel);
                channel->set_Index(KDeleted);
            }
            else
            {
                update(EPOLL_CTL_MOD, channel);
            }
        }
    }
    void EPollPoller::removeChannel(Channel *channel)
    {
        int fd = channel->fd();
        assert(channels_.find(fd) != channels_.end());
        assert(channels_[fd] == channel);
        assert(channel->isNoneEvent());
        int index = channel->index();
        assert(index == kAdded && index == KDeleted);
        size_t n = channels_.erase(fd);
        assert(n == 1);
        if (index == kAdded)
        {
            update(EPOLL_CTL_DEL, channel);
        }
        channel->set_Index(kNew);
    }

    void EPollPoller::fillActiveChannels(int numEvents, ChannelList *activeChannels) const
    {
        for (int i = 0; i < numEvents; ++i)
        {
            Channel *channel = static_cast<Channel *>(events_[i].data.ptr);
            channel->set_revents(events_[i].events);
            activeChannels->push_back(channel);
        }
    }
    void EPollPoller::update(int operation, Channel *channel)
    {
        struct epoll_event event;
        bzero(&event, sizeof(event));
        event.events = channel->events();
        event.data.ptr = channel;
        int fd = channel->fd();
        if (::epoll_ctl(epollfd_, operation, fd, &event) < 0)
        {
            if (operation == EPOLL_CTL_DEL)
            {
                LOG_SYSERR << " epoll_ctl op = " << operation << " fd= " << fd;
            }
            else
            {
                LOG_SYSFATAL << " epoll_ctl op = " << operation << " fd= " << fd;
            }
        }
    }

} // namespace tulun